package com.snap.update.update;


import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.NonNull;
import android.util.Log;


import com.orhanobut.logger.Logger;
import com.snap.update.AppConfig;
import com.snap.update.MoleApplication;

import com.snap.update.bean.SerialPortDevice;
import com.snap.update.utils.HexUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


import android_serialport_api.SerialPort;


/**
 * 这个类代表与一个串口的整个交互过程，从打开，到写数据、读数据，到最后的关闭连接
 */
public class UpdateSerialPortHelper {

    //消息种类
    public static final int MSG_RECEIVE = 0xff;
    public static final int MSG_QUEUE_SEND_START = 0xfe;
    public static final int MSG_SEND_TIMEOUT = 0xfd;
    private static final String TAG = "UpdateSerialPortHelper";
    private SerialPort mSerialPort;
    private SerialPortDevice mDevice;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    //一次接收的缓冲区，不能保证一次完整发送一包，有时候会2次发一个包
    private byte[] cache;
    //已发送
    private CmdBean sent;
    //待发送
    private Deque<CmdBean> deque = new LinkedBlockingDeque<>();
    private Handler workHandler;
    private AtomicBoolean isBusy = new AtomicBoolean(false);

    public UpdateSerialPortHelper(SerialPortDevice mDevice) {
        this.mDevice = mDevice;
        HandlerThread handlerThread = new HandlerThread(UpdateSerialPortHelper.class.getSimpleName());
        handlerThread.start();

        workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                int type = msg.what;
                switch (type) {
                    case MSG_QUEUE_SEND_START:
                        queueSendNext();
                        break;
                    case MSG_RECEIVE:
                        @NonNull
                        byte[] data = (byte[]) msg.obj;
                        if (sent == null) return;//说明当前的命令已经得到了响应或者已经超时
                        if (UpdateCmd.cmdMatch(sent.getCmd(), data)) {
                            msgRevert();
                            try {
                                sent.getCallback().onSuccess(data);
                            } catch (Exception e) {
                               Logger.e("callback.suc " + MoleApplication.printExceptionInfo(e));
                            }
                            sent = null;
                            try {
                                if (data[5] == 0x64 && data[6] == 0x04) {
                                    TimeUnit.MILLISECONDS.sleep(1200);
                                } else {
                                    TimeUnit.MILLISECONDS.sleep(5);
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            queueSendNext();
                        } else {
                            //不匹配的情况基本不会发生。
                            //已知发生的情况：模块号不对应  30模块的采光状态回复了01模块的温度 下位机问题
                            //发生了不应该视为这条指令失败，下位机可能马上又回复一条呢？所以只记录不作处理，不回复的话视为超时
                            Logger.e("receive cmd doesn't match send cmd!!! sent :"
                                    + HexUtils.ByteArrToHex(sent.getCmd())
                                    + " and receive :"
                                    + HexUtils.ByteArrToHex(data));
                        }
                        break;
                    case MSG_SEND_TIMEOUT:
                        if (sent != null) {
                            try {
                                sent.getCallback().onFailure(new TimeoutException("timeout! " + HexUtils.ByteArrToHex(sent.getCmd())));
                            } catch (Exception e) {
                                Logger.e("callback.fail " + MoleApplication.printExceptionInfo(e));
                            }
                            if (sent.getRetryCount() < 3) {
                                sent.retryOnce();
                                if (sent.isPriori()) {
                                    deque.addFirst(sent);
                                } else {
                                    deque.addLast(sent);
                                }
                            } else {
                                //重试超过3次就不占用发送通道了
                                try {
                                    Logger.e("timeout too many times! give up! " + HexUtils.ByteArrToHex(sent.getCmd()));
                                    sent.getCallback().onFailure(new Exception("timeout too many times! give up! " + HexUtils.ByteArrToHex(sent.getCmd())));
                                } catch (Exception e) {
                                    Logger.e("callback.fail " + MoleApplication.printExceptionInfo(e));
                                }
                            }
                            sent = null;
                        }
                        queueSendNext();
                        break;
                    default:
                }
            }
        };
    }

    /**
     * 队列取一条，发送
     */
    private void queueSendNext() {
        if (mOutputStream == null) return;
        if (deque.size() > 0) {
            CmdBean cmdBean = deque.remove();
            handleCallback(cmdBean);
            try {
                Log.d(TAG, "send: " + HexUtils.ByteArrToHex(cmdBean.getCmd()));
                mOutputStream.write(cmdBean.getCmd());
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            isBusy.set(false);
        }
    }

    private void handleCallback(CmdBean cmdBean) {
        msgRevert();
        sent = cmdBean;
        workHandler.sendMessageDelayed(workHandler.obtainMessage(MSG_SEND_TIMEOUT), 500);
    }

    private void msgRevert() {
        workHandler.removeMessages(MSG_SEND_TIMEOUT);
    }

    /**
     * 从串口读数据
     */
    private void onDataReceived(byte[] data) {
        if (data == null || data.length == 0) return;
        Log.e(TAG, "onDataReceived: " + HexUtils.ByteArrToHex(data));
        // 1.收到一条完整的指令
        if (UpdateCmd.cmdValidate(data)) {
            workHandler.sendMessage(workHandler.obtainMessage(MSG_RECEIVE, data));
            cache = null;
            return;
        }

        // 2.收到不正确/部分的指令
        if (cache == null || cache.length == 0) {//cache == null
            if (data.length == 1 && data[0] == AppConfig.Dynamic.CMD_START1) {
                cache = data;
            } else if (UpdateCmd.isOneMore(data)) {
                dealOneMoreCmd(data);
            } else if (data[0] == AppConfig.Dynamic.CMD_START1 && data[1] == AppConfig.Dynamic.CMD_START2) {
                cache = data;
            } else {
                cache = null;
            }
        } else {//cache != null
            //合并
            byte[] merge = new byte[cache.length + data.length];
            System.arraycopy(cache, 0, merge, 0, cache.length);
            System.arraycopy(data, 0, merge, cache.length, data.length);

            if (UpdateCmd.cmdValidate(merge)) {
                workHandler.sendMessage(workHandler.obtainMessage(MSG_RECEIVE, merge));
                cache = null;
            } else if (UpdateCmd.isOneMore(merge)) {
                dealOneMoreCmd(merge);
            } else if (merge[0] == AppConfig.Dynamic.CMD_START1 && merge[1] == AppConfig.Dynamic.CMD_START2) {
                //validate通过 等待继续接受
                cache = merge;
            } else {
                //validate不通过 cache置空
                cache = null;
            }
        }
    }

    private void dealOneMoreCmd(byte[] cmd) {
        while (UpdateCmd.isOneMore(cmd)) {
            int length = (cmd[4] & 0xff) + 2;
            byte[] split = new byte[length];
            System.arraycopy(cmd, 0, split, 0, length);
            byte[] rest = new byte[cmd.length - length];
            System.arraycopy(cmd, length, rest, 0, rest.length);
            if (UpdateCmd.cmdValidate(split)) {
                workHandler.sendMessage(workHandler.obtainMessage(MSG_RECEIVE, split));
            }
            cmd = rest;
        }
        if (cmd.length == 1 && cmd[0] == AppConfig.Dynamic.CMD_START1) {
            cache = cmd;
        } else if (cmd[0] == AppConfig.Dynamic.CMD_START1 && cmd[1] == AppConfig.Dynamic.CMD_START2) {
            cache = cmd;
        } else {
            cache = null;
        }
    }

    private void startQueueSendIfNeed() {
        //可能会发生2个线程同时进入isBusy==false
        //先比较 再更新值  是2步操作，不是原子操作 volatile boolean isBusy不能保证原子性
        //if(isBusy==false){
        //  isBusy=true;
        //  sendMessage();
        //}

        //原子操作,CAS实现线程安全，多线程情况下，保证只执行一次sendMessage
        if (isBusy.compareAndSet(false, true)) {
            workHandler.sendEmptyMessage(MSG_QUEUE_SEND_START);
        }
    }

    //👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇  public 方法 👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

    /**
     * 打开当前串口
     *
     * @throws SecurityException
     * @throws IOException
     */
    public void open() throws SecurityException, IOException {
        try {
            mSerialPort = new SerialPort(new File(mDevice.getPort()), mDevice.getBaudRate(), 0);
            Logger.e("open serial port suc");
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();
            mReadThread = new ReadThread();
            mReadThread.start();
        } catch (Exception e) {
            Logger.e("串口打开异常", e);
            throw e;
        }
    }

    /**
     * 关闭当前串口
     */
    public void close() {
        Logger.e("close serial port");
        if (mReadThread != null)
            mReadThread.interrupt();
        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;
        }
        try {
            if (mOutputStream != null) {
                mOutputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (mInputStream != null) {
                mInputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步发送指令
     * 按顺序发送，加入发送队列的末尾，
     */
    public void sendOrder(byte[] cmd, SpCallback callback) {
        deque.addLast(new CmdBean(cmd, callback));
        startQueueSendIfNeed();
    }

    /**
     * 异步发送指令
     * 优先发送，加入发送队列的前端
     */
    public void sendPriori(byte[] cmd, SpCallback callback) {
        CmdBean cmdBean = new CmdBean(cmd, callback);
        cmdBean.setPriori(true);
        deque.addFirst(cmdBean);
        startQueueSendIfNeed();
    }

    /**
     * 同步的改版
     * 发送指定(优先),并同步等待指令结果
     */
    public Bundle execute(byte[] cmd) throws InterruptedException {
        Bundle bundle = new Bundle();
        if (mOutputStream == null) {
            bundle.putString("msg", "outputStream is null");
            return bundle;
        }
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        SpCallback inner = new SpCallback() {
            @Override
            public void onSuccess(byte[] data) {
                bundle.putBoolean("suc", true);
                bundle.putByteArray("data", data);
                lock.lock();
                condition.signal();
                lock.unlock();
            }

            @Override
            public void onFailure(Exception e) {
                if (e instanceof TimeoutException) {
                    int timeout = bundle.getInt("timeout");
                    bundle.putInt("timeout", ++timeout);
                } else {
                    bundle.putString("msg", e.getMessage());
                    lock.lock();
                    condition.signal();
                    lock.unlock();
                }
            }
        };
        sendPriori(cmd, inner);
        lock.lock();
        try {
            boolean bool = condition.await(2200, TimeUnit.MILLISECONDS);
            if (!bool) bundle.putString("msg", "Communication timeout！");
        } finally {
            lock.unlock();
        }
        return bundle;
    }

    //厂商文档中写：不能保证一次完整发送一包，有时候会2次发一个包
    private class ReadThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                try {
                    if (mInputStream == null) return;
                    byte[] buffer = new byte[65536];//2字节 65536
                    int size = mInputStream.read(buffer);
                    if (size > 0) {
                        onDataReceived(Arrays.copyOf(buffer, size));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}